package com.heima.wemedia.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.SensitiveUtil;
import com.alibaba.fastjson.JSON;
import com.heima.aliyun.GreenImageScan;
import com.heima.aliyun.GreenTextScan;
import com.heima.apis.IArticleClient;
import com.heima.apis.IScheduleClient;
import com.heima.minio.MinIoTemplate;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.schedule.dto.Task;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.OcrUtil;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.service.AutoScanService;
import com.heima.wemedia.service.WmChannelService;
import com.heima.wemedia.service.WmUserService;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.TesseractException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AutoScanServiceImpl implements AutoScanService {

    @Autowired
    private WmNewsMapper wmNewsMapper;

    @Autowired
    private MinIoTemplate minIoTemplate;

    @Autowired
    private GreenTextScan greenTextScan;

    @Autowired
    private GreenImageScan greenImageScan;

    @Autowired
    private IArticleClient iArticleClient;

    @Autowired
    private WmChannelService wmChannelService;

    @Autowired
    private WmUserService wmUserService;

    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;

    @Override
    public void autoScanWmNews(Integer newsId) throws Exception {
        // 目标：实现一个方法，这个方法用来进行文章自动审核
        // 如果审核通过，就调用saveArticle方法，把新闻内容更新到移动端去
        // 如果审核不通过，就更新news表，把状态改为未通过

        // 该方法的上一个环节，是媒体后台里面的新增新闻接口
        // newsId指的是上一个环节 新增的那篇文章的id

        // 1. 入参判空
        if (newsId == null) {
            return;
        }

        // 2. 查询新闻信息是否存在
        // 2.1 新闻为空 -> 终止执行 -> 打印日志
        WmNews wmNews = wmNewsMapper.selectById(newsId);
        if (wmNews == null) {
            throw new RuntimeException("新闻不存在");
        }

        // 3. 提取新闻文字
        String text = getText(wmNews.getTitle(), wmNews.getContent());

        // 4. 提取新闻图片
        List<byte[]> images = getImages(wmNews.getImages(), wmNews.getContent());

        // 调用ocr识别图片中的文字，然后和新闻中的文字组装在一起，一起进行dfa和阿里云的文字校验
        String textFromImages = checkOcr(images);
        text = text + textFromImages;

        Boolean aBoolean = dfaCheck(text);
        if (!aBoolean) {
            return;
        }

        // 5. 调用文字审核接口
        Map map = greenTextScan.greeTextScan(text);
        // 5.1 审核失败 -> wmNews修改方法
        // 5.2 审核不确定 -> wmNews修改方法
        Boolean textScanResult = checkResult(map, newsId);
        if (!textScanResult) {
            return;
        }

        // 6. 调用图片审核接口
        // 6.1 审核失败 -> wmNews修改方法
        // 6.2 审核不确定 -> wmNews修改方法
        Map map1 = greenImageScan.imageScan(images);
        Boolean imageScanResult = checkResult(map1, newsId);
        if (!imageScanResult) {
            return;
        }

        // 7. 远程调用ArticleClient保存文章
        // 7.1 判断响应结果是不是200
        // wmNews -> ApArticle -> feign接口 saveArticle
        ArticleDto dto = wmNews2ArticleDto(wmNews);
        ResponseResult responseResult = iArticleClient.saveArticle(dto);
        if (responseResult == null || responseResult.getCode() != 200) {
            throw new RuntimeException("创建文章失败");
        }

        Object articleId = responseResult.getData();

        // 8. 将articleid回写到wmNews表中
        updateWmNews(9, "审核通过", (Long) articleId, newsId);
    }

    @Override
    public ArticleDto wmNews2ArticleDto(WmNews wmNews) {
        // 目标 wmNews -> ApArticleDto

        // 1. 入参判空
        if (wmNews == null) {
            return null;
        }

        // 2. 新建一个 ArticleDto 对象
        ArticleDto dto = new ArticleDto();
        // 3. 把相同的字段拷贝一份
        BeanUtils.copyProperties(wmNews, dto);
        // 4. 处理一下额外的字段
        // channelName查询频道的名称（根据频道id到频道表里面查）
        WmChannel channel = wmChannelService.getById(wmNews.getChannelId());
        if (channel != null) {
            dto.setChannelName(channel.getName());
        }
        // authorId, authorName -> 当前登录的用户id和名字
        Long userId = WmThreadLocalUtil.getUser().getId().longValue();
        dto.setAuthorId(userId);
        WmUser user = wmUserService.getById(userId);
        if (user != null) {
            dto.setAuthorName(user.getName());
        }
        // flag
        dto.setFlag((byte) 0);
        // layout
        dto.setLayout(wmNews.getType());
        // syncStatue
        dto.setSyncStatus(true);
        // createTime
        dto.setCreatedTime(new Date());
        return dto;
    }

    /**
     * 提取新闻文字内容
     *
     * @param title   标题
     * @param content 内容
     * @return 组合后的字符串
     */
    @Override
    public String getText(String title, String content) {
        // 目标：一篇新闻里面带文字的核心部分包含两块
        // 1. 标题， 2. 内容
        // 内容里面包含 图片地址 + 文字内容 ——>  把内容里面的图片筛选掉，只保留文字部分

        // 1.入参校验
        if (StrUtil.isBlank(title) && StrUtil.isBlank(content)) {
            return "";
        }

        // 2.提取content里面的文字内容，字符串转List<Map>
        // [{"type":"image","value":"rBEo4WOyRC6AYDTTAAArIxljsTE766.png"},{"type":"text","value":"第一行内容"}
        List<Map> maps = JSON.parseArray(content, Map.class);
        if (CollectionUtil.isEmpty(maps)) {
            return title;
        }

        StringBuilder builder = new StringBuilder();
        // 3.组装内容文字(内容+_+标题)
        // 将所有内容文字拼接成一个完整字符串，用,隔开
        for (Map map : maps) {
            if (map == null) {
                continue;
            }

            // key : value
            // 第一次遍历
            // type -> image
            // value -> rBEo4WOyRC6AYDTTAAArIxljsTE766.png
            // 第二次遍历
            // type -> text
            // value -> 第一行内容

            Object typeValue = map.get("type");
            if ("text".equals(typeValue)) {
                Object value = map.get("value");

                builder.append(value);
            }
        }

        // 4.将content文字内容和title进行组合
        builder.append("_").append(title);

        // 5.返回组合后的结果
        return builder.toString();
    }

    /**
     * 提取图片
     * 因为后续阿里云图片审核需要List<byte[]>结构，所以这里出参是这个类型
     *
     * @param images  封面图
     * @param content 内容
     * @return 图片byte集合
     */
    @Override
    public List<byte[]> getImages(String images, String content) {
        // 目标：content里面图片提取出来 和 封面 组装在一起形成一个集合
        // 当我们拿到图片url集合之后，还要把url转成byte[]

        // 0.入参判空
        if (StrUtil.isBlank(images) && StrUtil.isBlank(content)) {
            return new ArrayList<>();
        }

        // 1.声明总的图片集合
        List<String> urlList = new ArrayList<>();

        // 2.提取封面图 字符串images —> List<String>
        // images -> "1.png,2.png,3.png"
        // 转换后加入到总的图片集合中
        String[] split = images.split(",");
        List<String> coverImages = Arrays.asList(split);
        urlList.addAll(coverImages);

        // 3.提取内容图 -> List<String>
        // 3.1 将字符串转成List<Map>
        // 3.2 遍历并组装到总结的图片集合中
        List<Map> maps = JSON.parseArray(content, Map.class);
        // 如果内容图为空，那么就跳过内容图的处理
        if (!CollectionUtil.isEmpty(maps)) {
            for (Map map : maps) {
                if (map == null) {
                    continue;
                }

                Object type = map.get("type");
                if ("image".equals(type)) {
                    Object value = map.get("value");
                    if (value != null) {
                        urlList.add((String) value);
                    }
                }
            }
        }

        // urlList -> ["http://localhost:9000/1.png", "http://localhost:9000/2.png"]

        // 4.声明一个byte[]类型集合，组装最后要输出的结果
        // 因为阿里云校验方法，接收的是byte[]类型的图片
        List<byte[]> bytesList = new ArrayList<>();
        for (String url : urlList) {
            if (StrUtil.isBlank(url)) {
                continue;
            }

            // 5.遍历图片集合，转成List<byte[]>
            // 图片url想转byte[]类型，需要用到minio的downLoadFile方法
            byte[] bytes = minIoTemplate.downLoadFile(url);
            bytesList.add(bytes);
        }

        // 6.返回集合
        return bytesList;
    }

    /**
     * 更新新闻状态
     *
     * @param status    状态
     * @param reason    审核结果
     * @param articleId 文章id
     * @param newsId    新闻id（查询条件）
     * @return 是否成功
     */
    @Override
    public Boolean updateWmNews(Integer status, String reason, Long articleId, Integer newsId) {
        // 1.入参校验
        if (status == null || StrUtil.isBlank(reason) || newsId == null) {
            return false;
        }

        // 2.构建WmNews数据(id,status,reason,articleId)
        WmNews wmNews = wmNewsMapper.selectById(newsId);
        if (wmNews == null) {
            return false;
        }

        wmNews.setStatus(status.shortValue());
        wmNews.setReason(reason);
        if (articleId != null) {
            wmNews.setArticleId(articleId);
        }
        int i = wmNewsMapper.updateById(wmNews);
        if (i != 1) {
            return false;
        }

        // 3.执行更新新闻，并返回结果
        return true;
    }

    /**
     * 处理阿里云审核结果
     *
     * @param result 审核结果
     * @param newsId 新闻id
     * @return 是否通过
     */
    @Override
    public Boolean checkResult(Map result, Integer newsId) {
        // 1.入参判断
        if (result == null || newsId == null) {
            return false;
        }

        // key: value
        // suggestion: block
        Object suggestion = result.get("suggestion");
        // 2.返回信息{"suggestion":""}，包含三种类型的值
        // pass -> 通过
        if ("pass".equals(suggestion)) {
            return true;
        }

        // block -> 未通过 -> 更新数据库(状态:2, 原因:阿里云审核未通过)
        else if ("block".equals(suggestion)) {
            updateWmNews(2, "阿里云审核未通过", null, newsId);
            return false;
        }

        // review -> 不确定，转人工 -> 更新数据库(状态:3, 原因:转人工审核)
        else if ("review".equals(suggestion)) {
            updateWmNews(3, "转人工审核", null, newsId);
            return false;
        }

        // 3.返回结果
        return true;
    }

    /**
     * DFA算法检测敏感词
     *
     * @param text 待检测的文字
     * @return 是否通过检测（true为通过，false为不通过）
     */
    @Override
    public Boolean dfaCheck(String text) {
        // 1. 入参判断
        if (StrUtil.isBlank(text)) {
            return true;
        }

        // 2. 从数据库查出所有敏感词，字段(sensitives)
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(null);

        // 3. 提取所有字符串 -> List<String>
        List<String> collect = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());

        // 4. DFA初始化  SensitiveUtil.init(关键字集合)
        // 接收一个参数：参数是敏感词集合
        SensitiveUtil.init(collect);

        // 5. DFA敏感词匹配 SensitiveUtil.containsSensitive(字符串)
        // 接收一个参数：参数是要检测的那段话
        // 结果指的是：text这个字符串里面是否包含敏感词

        // 6. 将检测结果直接返回出去
        return SensitiveUtil.containsSensitive(text);
    }

    // 目标：把文章中的图片传进来，提取图片中可能存储在的文字信息
    // 将文字信息组装起来返回成一个字符串
    public String checkOcr(List<byte[]> imageList) throws IOException, TesseractException {
        if (CollectionUtil.isEmpty(imageList)) {
            return "";
        }

        StringBuilder builder = new StringBuilder();

        // 1. 遍历集合
        for (byte[] bytes : imageList) {
            // 2. byte[] -> BufferImage
            ByteArrayInputStream image = new ByteArrayInputStream(bytes);
            BufferedImage read = ImageIO.read(image);

            // 3. bufferImage -> ocr -> 提取文字
            String word = OcrUtil.doOcr(read);

            // 4. 每次循环得到的文字，组装在一起

            builder.append("_").append(word);
        }

        return builder.toString();
    }

    @Autowired
    private IScheduleClient scheduleClient;

    // 1. 设置每秒执行一次
    @Scheduled(fixedRate = 1000)
    @Override
    public void getTask() {
        // 1. 调试着获得一个任务
        // 注意：调用Feign接口后得到的结果是 ResponseResult
        // 想把结果转为可以解析的数据需要经过
        // getData() -> Object -> toJsonString() -> String -> parseObject() -> Task对象
        ResponseResult poll = scheduleClient.poll();

        // Task
        Object data = poll.getData();
        String jsonData = JSON.toJSONString(data);
        Task task = JSON.parseObject(jsonData, Task.class);

        // 2. 从Task对象中提取parameters字段
        String parameters = task.getParameters();
        WmNews wmNews = JSON.parseObject(parameters, WmNews.class);

        // 3. 调用审核代码
        try {
            autoScanWmNews(wmNews.getId());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}